package cn.snmp.mutual.uk.stack;

import lombok.extern.slf4j.Slf4j;
import cn.snmp.mutual.uk.util.SnmpUtilities;

import java.io.*;

@Slf4j
class AsnDecoderv3 extends AsnDecoderBase implements usmStatsConstants {
    private static final String version_id = "@(#)$Id: AsnDecoderv3.java,v 3.9 2009/03/05 12:48:59 birgita Exp $ Copyright Westhawk Ltd";

    int getMsgId(final AsnSequence asnTopSeq) throws DecodingException {
        int msgId = -1;
        final AsnSequence asnHeaderData = this.getAsnHeaderData(asnTopSeq);
        final AsnObject obj = asnHeaderData.getObj(0);
        if (obj instanceof AsnInteger) {
            final AsnInteger value = (AsnInteger) obj;
            msgId = value.getValue();
            return msgId;
        }
        final String msg = "msgId should be of type AsnInteger instead of " + obj.getRespTypeString();
        log.error(msg);
        throw new DecodingException(msg);
    }

    AsnSequence DecodeSNMPv3(final InputStream in) throws IOException, DecodingException {
        final AsnSequence asnTopSeq = this.getAsnSequence(in);
        final int snmpVersion = this.getSNMPVersion(asnTopSeq);
        if (snmpVersion != 3) {
            final String str = SnmpUtilities.getSnmpVersionString(snmpVersion);
            final String msg = "Wrong SNMP version: expected SNMPv3, received " + str;
            log.error(("$$$" + msg));
            throw new DecodingException(msg);
        }
        int securityModel = -1;
        final AsnSequence asnHeaderData = this.getAsnHeaderData(asnTopSeq);
        final AsnObject obj = asnHeaderData.getObj(3);
        if (!(obj instanceof AsnInteger)) {
            final String msg2 = "securityModel should be of type AsnInteger instead of " + obj.getRespTypeString();
            log.error(("$$$" + msg2));
            throw new DecodingException(msg2);
        }
        final AsnInteger value = (AsnInteger) obj;
        securityModel = value.getValue();
        if (securityModel != 3) {
            final String msg3 = "Wrong v3 Security Model: expected USM(3), received " + securityModel;
            log.error(("$$$" + msg3));
            throw new DecodingException(msg3);
        }
        return asnTopSeq;
    }

    AsnPduSequence processSNMPv3(final SnmpContextv3Basis context, final AsnSequence asnTopSeq, final byte[] message, final boolean amIAuthoritative) throws IOException, DecodingException {
        AsnPduSequence pduSeq = null;
        final boolean isCorrect = asnTopSeq.isCorrect;
        final AsnSequence asnHeaderData = this.getAsnHeaderData(asnTopSeq);
        final byte[] msgFlags = ((AsnOctets) asnHeaderData.getObj(2)).getBytes();
        final boolean isUseAuthentication = this.isUseAuthentication(msgFlags[0]);
        final boolean isUsePrivacy = this.isUsePrivacy(msgFlags[0]);
        final AsnOctets asnSecurityParameters = (AsnOctets) asnTopSeq.getObj(2);
        final AsnSequence usmObject = this.decodeUSM(asnSecurityParameters);
        final byte[] engineIdBytes = ((AsnOctets) usmObject.getObj(0)).getBytes();
        final String engineId = SnmpUtilities.toHexString(engineIdBytes);
        final int boots = ((AsnInteger) usmObject.getObj(1)).getValue();
        final int time = ((AsnInteger) usmObject.getObj(2)).getValue();
        final String userName = ((AsnOctets) usmObject.getObj(3)).getValue();
        final AsnOctets realFingerPrintObject = (AsnOctets) usmObject.getObj(4);
        final byte[] realFingerPrint = realFingerPrintObject.getBytes();
        final byte[] salt = ((AsnOctets) usmObject.getObj(5)).getBytes();
        final TimeWindow tWindow = TimeWindow.getCurrent();
        if (!amIAuthoritative) {
            if (engineId.length() > 0 && !tWindow.isEngineIdOK(context.getReceivedFromHostAddress(), context.getPort(), engineId)) {
                String msg = "Received engine Id ('" + engineId + "') is not correct.";
                msg += " amIAuthoritative == false";
                log.error(("$$$" + msg));
                throw new DecodingException(msg);
            }
            final String sendToHostAddress = context.getSendToHostAddress();
            final String receivedFromHostAddress = context.getReceivedFromHostAddress();
            if (!sendToHostAddress.equals(receivedFromHostAddress)) {
                final String storedEngineId = tWindow.getSnmpEngineId(sendToHostAddress, context.getPort());
                if (storedEngineId == null) {
                    tWindow.setSnmpEngineId(sendToHostAddress, context.getPort(), "00");
                }
            }
        } else if (engineId.length() > 0) {
            final TimeWindow timeWindow = tWindow;
            context.getUsmAgent();
            if (!timeWindow.isEngineIdOK("_myusmagent", context.getPort(), engineId)) {
                String msg = "Received engine Id ('" + engineId + "') is not correct.";
                msg += " amIAuthoritative == true";
                log.error(("$$$" + msg));
                throw new DecodingException(msg);
            }
        }
        if (!userName.equals(context.getUserName())) {
            final String msg = "Received userName ('" + userName + "') is not correct";
            log.error(("$$$" + msg));
            throw new DecodingException(msg);
        }
        DecodingException encryptionDecodingException = null;
        IOException encryptionIOException = null;
        try {
            final AsnObject asnScopedObject = asnTopSeq.getObj(3);
            AsnSequence asnPlainScopedPdu = null;
            if (isUsePrivacy) {
                byte[] privKey = null;
                final int prot = context.getAuthenticationProtocol();
                if (prot == 0) {
                    final byte[] passwKey = context.getPrivacyPasswordKeyMD5();
                    privKey = SnmpUtilities.getLocalizedKeyMD5(passwKey, engineId);
                } else {
                    final byte[] passwKey = context.getPrivacyPasswordKeySHA1();
                    privKey = SnmpUtilities.getLocalizedKeySHA1(passwKey, engineId);
                }
                final AsnOctets asnEncryptedScopedPdu = (AsnOctets) asnScopedObject;
                final byte[] encryptedText = asnEncryptedScopedPdu.getBytes();
                byte[] plainText = null;
                final int pprot = context.getPrivacyProtocol();
                if (pprot == 3) {
                    plainText = SnmpUtilities.AESdecrypt(encryptedText, privKey, boots, time, salt);
                } else {
                    plainText = SnmpUtilities.DESdecrypt(encryptedText, salt, privKey);
                }
                if (AsnObject.debug > 10) {
                    System.out.println("Encrypted PDU: ");
                    System.out.println("Decoding with : " + SnmpContextv3Basis.ProtocolNames[pprot]);
                }
                log.error("Encrypted PDU: ");
                log.error("Decoding with : " + SnmpContextv3Basis.ProtocolNames[pprot]);
                final ByteArrayInputStream plainIn = new ByteArrayInputStream(plainText);
                asnPlainScopedPdu = this.getAsnSequence(plainIn);
            } else {
                asnPlainScopedPdu = (AsnSequence) asnScopedObject;
            }
            final byte[] contextId = ((AsnOctets) asnPlainScopedPdu.getObj(0)).getBytes();
            final String contextName = ((AsnOctets) asnPlainScopedPdu.getObj(1)).getValue();
            pduSeq = (AsnPduSequence) asnPlainScopedPdu.findPdu();
        } catch (DecodingException exc) {
            log.error(("$$$ AsnDecoderv3 processSNMPv3" + exc));
            encryptionDecodingException = exc;
        } catch (IOException exc2) {
            log.error(("$$$ AsnDecoderv3 processSNMPv3 " + exc2));
            encryptionIOException = exc2;
        }
        if (pduSeq != null && engineId.length() == 0) {
            pduSeq.setSnmpv3Discovery(true);
        }
        final boolean userIsUsingAuthentication = context.isUseAuthentication();
        if (isCorrect && isUseAuthentication != userIsUsingAuthentication) {
            String msg2 = "User " + userName + " does ";
            if (!userIsUsingAuthentication) {
                msg2 += "not ";
            }
            msg2 += "support authentication, but received message ";
            if (isUseAuthentication) {
                msg2 += "with authentication.";
            } else {
                msg2 += "without authentication";
                msg2 += this.getUsmStats(pduSeq);
            }
            log.error(("$$$ AsnDecoderv3 processSNMPv3 " + msg2));
            throw new DecodingException(msg2);
        }
        boolean isAuthentic = false;
        if (isCorrect && isUseAuthentication) {
            final int fpPos = realFingerPrintObject.getContentsPos();
            if (AsnObject.debug > 10) {
                final int fpLength = realFingerPrintObject.getContentsLength();
                final String str = "Pos finger print = " + fpPos + ", len = " + fpLength;
                SnmpUtilities.dumpBytes(str, realFingerPrint);
            }
            byte[] calcFingerPrint = null;
            System.arraycopy(AsnEncoderv3.dummyFingerPrint, 0, message, fpPos, realFingerPrint.length);
            final int prot2 = context.getAuthenticationProtocol();
            if (prot2 == 0) {
                final byte[] passwKey2 = context.getAuthenticationPasswordKeyMD5();
                final byte[] authkey = SnmpUtilities.getLocalizedKeyMD5(passwKey2, engineId);
                calcFingerPrint = SnmpUtilities.getFingerPrintMD5(authkey, message);
            } else {
                final byte[] passwKey2 = context.getAuthenticationPasswordKeySHA1();
                final byte[] authkey = SnmpUtilities.getLocalizedKeySHA1(passwKey2, engineId);
                calcFingerPrint = SnmpUtilities.getFingerPrintSHA1(authkey, message);
            }
            if (!SnmpUtilities.areBytesEqual(realFingerPrint, calcFingerPrint)) {
                final String msg3 = "Authentication comparison failed";
                log.error(("$$$ AsnDecoderv3 processSNMPv3 " + msg3));
                throw new DecodingException(msg3);
            }
            if (pduSeq != null && boots == 0 && time == 0) {
                pduSeq.setSnmpv3Discovery(true);
            }
            if (tWindow.isOutsideTimeWindow(engineId, boots, time)) {
                final String msg3 = "Message is outside time window";
                log.error(("$$$ AsnDecoderv3 processSNMPv3 " + msg3));
                throw new DecodingException(msg3);
            }
            isAuthentic = true;
        }
        tWindow.updateTimeWindow(engineId, boots, time, isAuthentic);
        final boolean userIsUsingPrivacy = context.isUsePrivacy();
        if (isCorrect && isUsePrivacy != userIsUsingPrivacy) {
            String msg4 = "User " + userName + " does ";
            if (!userIsUsingPrivacy) {
                msg4 += "not ";
            }
            msg4 += "support privacy, but received message ";
            if (isUsePrivacy) {
                msg4 += "with privacy.";
            } else {
                msg4 += "without privacy";
                msg4 += this.getUsmStats(pduSeq);
            }
            log.error(("$$$ AsnDecoderv3 processSNMPv3 " + msg4));
            throw new DecodingException(msg4);
        }
        if (encryptionDecodingException != null) {
            throw encryptionDecodingException;
        }
        if (encryptionIOException != null) {
            throw encryptionIOException;
        }
        if (pduSeq != null && !isCorrect) {
            pduSeq.isCorrect = false;
        }
        return pduSeq;
    }

    private boolean isUseAuthentication(final byte msgFlags) {
        final boolean isUseAuthentication = (0x1 & msgFlags) > 0;
        return isUseAuthentication;
    }

    private boolean isUsePrivacy(final byte msgFlags) {
        final boolean isUsePrivacy = (0x2 & msgFlags) > 0;
        return isUsePrivacy;
    }

    private AsnSequence decodeUSM(final AsnOctets asnSecurityParameters) throws IOException {
        final byte[] usmBytes = asnSecurityParameters.getBytes();
        if (AsnObject.debug > 10) {
            SnmpUtilities.dumpBytes("Decoding USM:", usmBytes);
        }
        final ByteArrayInputStream usmIn = new ByteArrayInputStream(usmBytes);
        final AsnSequence usmOctets = new AsnSequence(usmIn, usmBytes.length, asnSecurityParameters.getContentsPos());
        final AsnSequence usmObject = (AsnSequence) usmOctets.getObj(0);
        return usmObject;
    }

    private String getUsmStats(final AsnPduSequence pduSeq) {
        String msg = "";
        final AsnSequence varBind = (AsnSequence) pduSeq.getObj(3);
        final int size = varBind.getObjCount();
        if (size > 0) {
            final AsnSequence varSeq = (AsnSequence) varBind.getObj(0);
            final Varbind vb = new Varbind(varSeq);
            AsnObjectId oid;
            boolean found;
            int i;
            AsnObjectId usmOid;
            for (oid = vb.getOid(), found = false, i = 0; i < AsnDecoderv3.usmStatsOids.length && !found; found = oid.startsWith(usmOid), ++i) {
                usmOid = new AsnObjectId(AsnDecoderv3.usmStatsOids[i]);
            }
            if (found) {
                --i;
                msg = msg + ": " + AsnDecoderv3.usmStatsStrings[i] + " " + vb.getValue();
            } else {
                msg = msg + ": " + vb;
            }
        }
        return msg;
    }
}
